#!/usr/bin/env python3

import os
import shlex
import subprocess
import threading

from shell_helpers import LF
import common
import thread_pool

class Main(common.BuildCliFunction):
    def __init__(self, *args, **kwargs):
        if not 'description' in kwargs:
            kwargs['description'] = '''\
Build our compiled userland examples.
'''
        super().__init__(*args, **kwargs)
        self.add_argument(
            'targets',
            default=[],
            help='''\
Select to build only the given userland programs, or all programs under
the given directories.

Default: build all.

Must point to either sources or directories under userland/, or to LKMC
toplevel which is a synonym for userland/.

Default: build all examples that have their package dependencies met, e.g.:

-   userland/arch/ programs only build if the target arch matches
-   an OpenBLAS example can only be built if the target root filesystem
    has the OpenBLAS libraries and headers installed, which you must inform
    with --package
''',
            nargs='*',
        )
        self._add_argument('--ccflags')
        self._add_argument('--force-rebuild')
        self._add_argument('--optimization-level')

    def build(self):
        build_dir = self.get_build_dir()
        cc_flags = [
            '-I', self.env['root_dir'], LF,
            '-O{}'.format(self.env['optimization_level']), LF,
        ] + self.sh.shlex_split(self.env['ccflags'])
        if self.env['static']:
            cc_flags.extend(['-static', LF])
        extra_obj_lkmc_common = os.path.join(
            build_dir,
            self.env['common_basename_noext'] + self.env['obj_ext']
        )
        self._build_one(
            in_path=self.env['common_c'],
            out_path=extra_obj_lkmc_common,
            cc_flags=cc_flags,
            extra_deps=[self.env['common_h']],
            link=False,
        )
        extra_obj_userland_asm = os.path.join(
            build_dir,
            'arch',
            'main' + self.env['obj_ext']
        )
        extra_obj_userland_asm_relpath = os.path.join(
            'arch',
            'main' + self.env['c_ext']
        )
        self._build_one(
            in_path=os.path.join(
                self.env['userland_source_dir'],
                extra_obj_userland_asm_relpath
            ),
            out_path=extra_obj_userland_asm,
            cc_flags=cc_flags,
            extra_deps=[self.env['common_h']],
            link=False,
        )
        with thread_pool.ThreadPool(
            self._build_one,
            nthreads=self.env['nproc'],
        ) as my_thread_pool:
            try:
                for target in self.env['targets']:
                    for path, in_dirnames, in_filenames in self.sh.walk(target):
                        for in_filename in in_filenames:
                            in_ext = os.path.splitext(in_filename)[1]
                            if not in_ext in self.env['build_in_exts']:
                                continue
                            in_path = os.path.join(path, in_filename)
                            error = my_thread_pool.submit({
                                'in_path': in_path,
                                'out_path': self.resolve_userland_executable(in_path),
                                'cc_flags': cc_flags,
                                'extra_objs_lkmc_common': [extra_obj_lkmc_common],
                                'extra_objs_userland_asm': [extra_obj_userland_asm],
                            })
                            if error is not None:
                                raise common.ExitLoop()
            except common.ExitLoop:
                pass
        error = my_thread_pool.get_error()
        if error is not None:
            print(error)
            return 1
        if not self.env['in_tree']:
            self.sh.copy_dir_if_update(
                srcdir=build_dir,
                destdir=self.env['out_rootfs_overlay_lkmc_dir'],
                filter_ext=self.env['userland_executable_ext'],
            )
        return 0

    def clean(self):
        if self.env['in_tree']:
            for target in self.env['targets']:
                if os.path.exists(target):
                    if os.path.isfile(target):
                        self.sh.rmrf(self.resolve_userland_executable(target))
                    else:
                        for path, dirnames, filenames in self.sh.walk(target):
                            for filename in filenames:
                                if os.path.splitext(filename)[1] in self.env['userland_out_exts']:
                                    self.sh.rmrf(os.path.join(path, filename))
        else:
            for target in self.env['targets']:
                self.sh.rmrf(self.resolve_userland_executable(target))

    def get_build_dir(self):
        return self.env['userland_build_dir']

    def setup_one(self):
        self.env['targets'] = self.resolve_targets(
            self.env['userland_source_dir'],
            self.env['targets']
        )

if __name__ == '__main__':
    Main().cli()
